import visapatchref
from visa import instrument
import numpy as np
import time
#import string

"""
--------------------------------------------------------------------
 Classes to control the operation of the Keithley 2410 sourcemeter or the
 6517A electrometer.
 
 The commands implemented are not comprehensive.
 
 Note: Not all functions have been tested.
 
 My general convention (some based on Python convention...):
 
   .__function__()  built-in/reserved methods
   ._variable_      variables that are not callable...i.e. no '()' at end
   .InternalFunc()  initial capital letter indicates the function is called internally only
   .someCommand()   perform instrument commands/queries
   .MACRO_FUNC()    "macro" functions that do a whole bunch of things, like set up the instrument
   

Revision History:
11 Aug 2010 (LNT)
 - Adjusted reset sleep to 3 seconds
10 Aug 2010 (LNT)
 - Added visapatch functionality to replace "serial" initialization with one that
   uses the serial port in a way compatible with the rest of the pyvisa-dependent code
 - Removed initial printing to avoid confusion
 - Added 'SCRIPT:' prefix to messages generated by the script and not the instrument
9 Aug 2010 (LNT)
 - Changed initialization so that arguments > 5 will initialize as COM ports
    This allows the other GPIB/USB converter to act as a virtual COM port on
    my computer
 - Changed initialization to use PyVISA instead of Serial package for compliance
    with the rest of the class
3 Feb 2010
 - A change in "sourcemeter"'s "getN" to reduce the number of sent commands from
   two to one under normal data collection conditions...faster.
 - New "sourcemeter" attribute "_armCount_" initialized in "__init__" and used/
   modified by ".getN".
 - New "sourcemeter" function ".cvSense()" and macro ".SETUP_FOR_PV(Imax)". 
   Changed ".getVoltCurrN()" to ".getVI()", now defaulting to N=1.
 - Some bug fixes from instrument testing.
 - ** New class "InstrTestingClass" with functions ".SpeedTest()" and 
   ".speedComparison_24xx()"
2 Feb 2010
 - Added a new input to "sourcemeter" class "fasterMeas", defaulting to False to
   maintain backward compatibility. Saved as an attribute in "__init__", and the
   "getXXXX()" functions now use this attribute to decide what to do.
 - reverted the so-called revolution in "vSense" et al made on 11 Jan by using
   the new "fasterMeas" attribute to keep backward compatibility.
 - Moved the redundant logic in "getXXXXN_fast" to new function "getN".
15 Jan 2010
 - Fixed up "getVoltsN_fast" and added "getCurrentN_fast" and "getVoltCurrN".
 - Added helper functions "ParseAscii" and "FlipVector"
11 Jan 2010
 - Added "outputOffState" to "sourcemeter" class.
 - A minor revolution in the "sourcemeter" "vSense", "cSense", and "rSense" fns.
 - The main ".k_ser." method in "sourcemeter" changed to "raw". Same for 
   ".inst." in "electrometer".
 - Changed "getVolts" et al in "sourcemeter" to just .ask, not .write then 
   .read.  I did this in response to some buggy operation playing with the 2420
   when using the old write and read algorithm.
25 Nov 2009
 - Big changes to "electrometer" class, particularly in .__init__, .ParseLine, 
 and .GetAnything functions.
24 Nov 2009
 - Added a bunch more untested functions to "electrometer" class.
18 Nov 2009
 - Added a bunch of untested functions to "electrometer" class.
27 Oct 2009
 - Updated "cSourceRange" and "vSourceRange" functions in
   "sourcemeter" class.
26 Oct 2009
 - Changed name of module (i.e. filename) to "k_meter_ntg".
 - Changed name of "K_Meter" class to "sourcemeter"
 - Added a new "electrometer" class, but it only has functions 
   __init__, openport, and closeport at this point, copied from
   "sourcemeter".
 - Added "getVoltsN", "getCurrentN", and "getResistanceN" 
   functions to "sourcemeter" class, *which require NumPy*.
25 Oct 2009
 - Strongly worded comments added in the definition of 
   "fourWire", and added automatic enable of minimum source
   voltage protection.
 - Added "vProtect" function.
 - Added "vSourceRange" and "cSourceRange" functions
24 Oct 2009
 - Added 2-wire and 4-wire sense modes
23 Oct 2009
 - Added setup commands to set output off-state
 - Fixed a bug in "cRange" definition
Merlin's revision history:
# [5/28/2009] Added default GPIB for init, display enable/disable functions.
# [8/17/2008] Small bug fixes for reading data from the sourcemeter over GPIB.
# [7/24/2008] Added initialization routines for GPIB.
#             Forked from M. Manfred's original code. 


USAGE EXAMPLE
 For operation over serial (RS-232) port COMn, pass the declaration n-1. 
 (e.g. if the instrument is on COM1, use sourcemeter(0).) Do not use ports higher than COM2. 
 For operation over GPIB, pass it the address number (the device's default is 24).

 import k_meter_ntg

 keith=k_meter_ntg.sourcemeter(24) # for GPIB channel 24 
 keith.setVolts(2)
 keith.ON()

 keith.OFF()
 keith.closeport()
--------------------------------------------------------------------
"""

## ****************************************************************************
##  A class for the Keithley 2410 SourceMeter, or any 24xx series
## ****************************************************************************
class sourcemeter():

    def __init__(self, portNum=24, comclass=None, fasterMeas=False):
        ## if portNum = 0 or 1, assumed to be COM; otherwise, GPIB address
        ## defaults to GPIB address 24
        self._serPort_ = None
        self._GPIBaddr_ = None
        ## A new input, stored as an attribute for use in functions to retain backward compatibility
        self._fasterMeas_ = fasterMeas
        self._armCount_ = None  # initialize this variable, used by ".getN"
        ## Port setup
        if comclass:
            self.raw = visapatchref.Visapatch_Serial(portNum,comclass)
##             self.raw = serial.Serial(self._serPort_,9600,timeout=1)
        else:
            self._GPIBaddr_ = portNum            
            self.raw = instrument('GPIB::' + str(self._GPIBaddr_) + '::INSTR')
##         print('SCRIPT: SourceMeter initialized on port '+str(portNum))
        ## Get some values we need by querying the instrument
        self._armCount_ = int(self.raw.ask(':arm:seq:coun?'))
        self._trigCount_ = int(self.raw.ask(':trig:seq:coun?')) # not used at this time
    

    def openport(self):
        if self._GPIBaddr_ == None:
            self.raw = visapatch.Visapatch_Serial(self._serPort_)
        else:
            self.raw = instrument('GPIB::' + str(self._GPIBaddr_) + '::INSTR')

    def closeport(self):
        self.raw.close()


## ----------------------------------------------
## measurement/sourcing of voltage and current
## ----------------------------------------------

    def FlipVector(self,vectorIn):
        """
        Reverse the order of a NumPy vector (e.g. LIFO to FIFO)
        
        See "...reverse order..." on http://www.scipy.org/NumPy_for_Matlab_Users
        """
        vectorOut=vectorIn[::-1]
        vectorOut
    
    def ParseAscii(self,allMeas,N=None):
        """
        Use this function to parse the buffer after a :read? command.
        
        In ASCII mode, it comes out 'voltage, current, resistance, time, status' 
        (manual  p. 429)
        
        The trouble is, it is also *backward* in a LIFO sense; e.g. voltsN[0]
        will be the most RECENT reading, not the first.  We also account for 
        that and switch it using FlipVector().
        """
        if N == None:
            N = (allMeas.count(',')+1)/5 # count the commas to get the number of measurements
        voltsN=np.zeros(N); ampsN=np.zeros(N); ohmsN=np.zeros(N); timeN=np.zeros(N)
        measList = allMeas.split(',') # get a list of measurements
        for ii in range(N):
            voltsN[ii] = float(measList[ii*5]) # every 5th entry starting at [0] is voltage
            ampsN[ii] = float(measList[ii*5+1]) # " starting at [1] is current
            ohmsN[ii] = float(measList[ii*5+2]) # " starting at [2] is resistance
            timeN[ii] = float(measList[ii*5+3]) # " starting at [3] is timestamp
        ## Make them FIFO
        #voltsN = self.FlipVector(voltsN); ampsN = self.FlipVector(ampsN);
        #ohmsN = self.FlipVector(ohmsN); timeN = self.FlipVector(timeN); 
        return (voltsN,ampsN,ohmsN,timeN)
    
    def getVolts(self):
        """
        Measure one sample of voltage and return the string.
        
        If it doesn't work:
         - If you're using old-school commands, make sure to run ".vSense()" first 
         OR
         - If you're in "fasterMeas" mode or want to be, 
           run ".getVoltsN_fast(Enable=True)"
        """
        if self._fasterMeas_:
            line = str(self.getVoltsN_fast(1))
        else:
            line = self.raw.ask(':MEAS:VOLT?\n')
        return line
        
    def getCurrent(self):
        """
        Measure one sample of current and return the string.
        
        If it doesn't work:
         - If you're using old-school commands, make sure to run ".cSense()" first 
         OR
         - If you're in "fasterMeas" mode or want to be, 
           run ".getCurrentN_fast(Enable=True)"
        """
        if self._fasterMeas_:
            line = str(self.getVoltsN_fast(1))
        else:
            line = self.raw.ask(':MEAS:CURR?\n')
        return line
        
    def getResistance(self):
        """
        Measure one sample of resistance and return the string.
        """
        line = self.raw.ask(':MEAS:RES?\n')
        return line
        
    def getN(self,N=10,volt=True,curr=False,Enable=False,turnOthersOFF=True):
        """
        The workhorse for getVoltsN_fast and getCurrentN_fast.  
        
        See Keithley 24xx PDF manual 9-5 (p. 215) for some other ways to do this.
        
        Input notes:
        - "volt" and "curr" and be True simultaneously
        - "N" will only have an effect if "Enable" is set to True OR (hopefully)
          if you change it.
        - "turnOthersOFF" will only do something if "Enable" is set to True.
        """
        ## Deal with the sense function settings, as needed
        if Enable:
            if turnOthersOFF:
                self.raw.write(':func:off:all') # turn everything off first
            ## Let's minimize the number of GPIB Tx's with some redundant logic
            if volt&curr:
                self.raw.write(':func:on "CURR","VOLT"') # enable voltage sensing
            else:
                if volt:
                    self.raw.write(':func:on "VOLT"') # enable current sensing
                if curr:
                    self.raw.write(':func:on "CURR"') # enable current sensing
            self._fasterMeas_ = True # write attribute to tell other functions how to read data
            self.ResetFormat()
        ## Setup trigger count
        #print('N = '+str(N)); print('_armCount_ = '+str(self._armCount_))
        countChanged = N != self._armCount_  # True if "N" different from "_armCount_"
        if Enable|countChanged:
            self.raw.write(':arm:seq:coun '+str(N)) # set the trigger count to N meas
            self._armCount_ = N  # update the attribute for next time
            print('Updated ARM count to '+str(N)) # temporary, for debugging
        ## Trigger and read what's in the buffer
        #self.raw.write(':init') # trigger the measurements (output must be ON)
        allMeas = self.raw.ask(':read?') # trigger and read what's in the buffer
        ## Now need to parse the buffer
        v_c_r_t = self.ParseAscii(allMeas)
        return v_c_r_t
        #return allMeas
        
    def getEnabledFuncs(self):
        """
        Returns a list of currently enabled functions
        """
        currFuncs = self.raw.ask(':func:on?')
        return currFuncs
        
    def getVoltsN(self,N=10,inclTime=False):
        """
        Measure N samples of voltage and return the array.
        
        Later updated this to be faster, but kept backward compatiblity by 
        using the attribute "._fasterMeas_".
        (The "inclTime" input only has an effect if "._fasterMeas_" is True.)
        """
        if self._fasterMeas_:
            voltsN = self.getVoltsN_fast(N,inclTime=inclTime)
        else:
            voltsN = np.zeros(N)  # initialize the array
            for ii in range(N):
                self.raw.write(":MEAS:VOLT?\n")
                line = self.raw.read()
                voltsN[ii] = float(line)
        return voltsN
        
    def getCurrentN(self,N=10,inclTime=False):
        """
        Measure N samples of current and return the array.
        
        (The "inclTime" input only has an effect if "._fasterMeas_" is True.)
        """
        if self._fasterMeas_:
            ampsN = self.getCurrentN_fast(N,inclTime=inclTime)
        else:
            ampsN = np.zeros(N)  # initialize the array
            for ii in range(N):
                self.raw.write(":MEAS:CURR?\n")
                line = self.raw.read()
                ampsN[ii] = float(line)
        return ampsN
        
    def getVoltsN_fast(self,N=10,Enable=False,turnOthersOFF=True,inclTime=False):
        """
        Measure N samples of voltage and return the array, the right way
        
        Output:
                IF "inclTime" is True: a tuple (voltsN, timeN)
                ELSE: a 1D array voltsN
        """
        ## use shared workhorse function, above
        v_c_r_t = self.getN(N,volt=True,curr=False,Enable=Enable,turnOthersOFF=turnOthersOFF)
        voltsN = v_c_r_t[0]
        if inclTime:
            return (voltsN,v_c_r_t[3]) 
        else:
            return voltsN
    
    def getCurrentN_fast(self,N=10,Enable=False,turnOthersOFF=True,inclTime=False):
        """
        Measure N samples of current and return the array, the right way
        
        Output:
                IF "inclTime" is True: a tuple (ampsN, timeN)
                ELSE: a 1D array ampsN
        """
        ## use shared workhorse function, above
        v_c_r_t = self.getN(N,volt=False,curr=True,Enable=Enable,turnOthersOFF=turnOthersOFF)
        ampsN = v_c_r_t[1]
        if inclTime:
            return (ampsN,v_c_r_t[3]) 
        else:
            return ampsN
    
    def getVI(self,N=1,Enable=False,turnOthersOFF=True,inclTime=False):
        """
        Measure N samples of BOTH current and voltage and return the arrays
        
        Returns a tuple (voltsN, ampsN) or (voltsN, ampsN, timeN)
        """
        ## use shared workhorse function, above
        v_c_r_t = self.getN(N,volt=True,curr=True,Enable=Enable,turnOthersOFF=turnOthersOFF)
        voltsN = v_c_r_t[0]; ampsN = v_c_r_t[1]; timeN = v_c_r_t[3]
        if inclTime:
            return (voltsN,ampsN,timeN)
        else:
            return (voltsN,ampsN)

    def getResistanceN(self,N=10):
        """
        Measure N samples of resistance and return the array.
        """
        ohmsN = np.zeros(N)  # initialize the array
        for ii in range(N):
            self.raw.write(":MEAS:RES?\n")
            line = self.raw.read()
            ohmsN[ii] = float(line)
        return ohmsN

    def setVolts(self,volts):
        """
        Set source voltage.
        """
        self.raw.write(":SOUR:VOLT "+str(volts)+"\n")

    def setCurrent(self,current):
        """
        Set source current.
        """
        self.raw.write(":SOUR:CURR "+str(current)+"\n")

    def ON(self):
        self.raw.write(":OUTPUT:STAT ON\n")

    def OFF(self):
        self.raw.write(":OUTPUT:STAT OFF\n")
        
    def continuousON(self):
        """
        Turns on continuous trigger mode, analagous to default local operation.
        
        NOTE THAT "getVolts" et al will not work in this mode...yet.
        """
        self.raw.write(':arm:seq:coun INF') # setup
        self._armCount_ = 'inf'
        self.ON() # output needs to be ON to send the next command...
        self.raw.write(':init') # start continuous mode
        
    def continuousOFF(self):
        self.raw.write(':abor') # abort continuous triggering
        self.raw.write(':arm:seq:coun 1') # reset the count back to 1
        self._armCount_ = 1
        self.OFF() # turn the output off

## ----------------------------------------------
## Meter setup commands
## ----------------------------------------------

    def reset(self):
        """
        Reset the meter to defaults (actually possibly any SCPI instrument on 
        the bus?).
        
        Note this does NOT reset the timestamp to 0, use .timeReset() to do that.
        """
        self.raw.write("*RST\n")
        time.sleep(15)
        self._armCount_ = int(self.raw.ask(':arm:seq:coun?'))
        self._trigCount_ = int(self.raw.ask(':trig:seq:coun?')) # not used at this time
        
    def timeQuery(self):
        """
        Returns present value of instrument timestamp in seconds.
        """
        time_s = float(self.raw.ask(':syst:time?'))
        return time_s
        
    def timeReset(self):
        """
        Reset the instrument timestamp to 0.
        """
        self.raw.write(':syst:time:res')
        
    def timeAutoResetON(self):
        """
        Auto-reset the instrument timestamp upon each trigger event (actually 
        each time the instrument exits "idle" mode).
        """
        self.raw.write(':syst:time:res:auto 1')
        
    def timeAutoResetOFF(self):
        """
        Don't reset timestamp upon each trigger event (default behavior).
        """
        self.raw.write(':syst:time:res:auto 0')

    # Compliance levels
    def vCompliance(self,volts):
        self.raw.write(":SENS:VOLT:PROT "+str(volts)+"\n")

    def cCompliance(self,current):
        self.raw.write(":SENS:CURR:PROT "+str(current)+"\n")
        
    ## Sense mode (see pp. 440+ of 24xx manual PDF)
    def ResetFormat(self):
        """
        Reset to the default buffer output format; used by functions ".vSense()",
        ".cSense()", and ".rSense()" when we are in mode "fasterMeas".  
        Also used by ".getN" function.
        """
        self.raw.write(':form:elem:sens VOLT, CURR, RES, TIME, STAT')
    
    def vSense(self):
        """
        Set up for voltage sensing.  (Action depends on "fasterMeas" status.)
        """
        if self._fasterMeas_: # turn on voltage sense mode for buffered I/O
            self.raw.write('SENS:FUNC:OFF "RES"') # need to turn off RES mode
            self.raw.write('SENS:FUNC:OFF "CURR"') # ...and turn off CURR mode
            self.raw.write('SENS:FUNC:ON "VOLT"')
            self.ResetFormat()
        else: # use voltage mode in old-school fashion
            ## This just changes the buffer output format so only the 
            ## voltage string is output
            self.raw.write('SENS:FUNC "VOLT"')
            self.raw.write("FORM:ELEM:SENS VOLT\n")

    def cSense(self):
        """
        Set up for current sensing.  (Action depends on "fasterMeas" status.)
        """
        if self._fasterMeas_: # turn on current sense mode for buffered I/O
            self.raw.write('SENS:FUNC:OFF "RES"') # need to turn off RES mode
            self.raw.write('SENS:FUNC:OFF "VOLT"') # ...and turn off VOLT mode
            self.raw.write('SENS:FUNC:ON "CURR"')
            self.ResetFormat()
        else:
            ## This just changes the buffer output format so only the 
            ## current string is output
            self.raw.write("FORM:ELEM:SENS CURR\n")
            
    def cvSense(self):
        """
        Set up for current AND voltage sensing. (Action depends on "fasterMeas" status.)
        """
        if self._fasterMeas_: # turn on current sense mode for buffered I/O
            self.raw.write('SENS:FUNC:OFF "RES"') # need to turn off RES mode
            self.raw.write('SENS:FUNC:ON "VOLT"') # Turn on VOLT mode
            self.raw.write('SENS:FUNC:ON "CURR"') # ...and CURR mode
            self.ResetFormat()
        else:
            ## This just changes the buffer output format so only the 
            ## current string is output
            #self.raw.write("FORM:ELEM:SENS CURR\n")
            print('\nThis mode is not supported unless ._fasterMeas_ = True.\n')
        
    def rSense(self):
        """
        Set up for resistance sensing.  (Action depends on "fasterMeas" status.)
        """
        if self._fasterMeas_: # turn on resistance sense mode for buffered I/O
            self.raw.write('SENS:FUNC:ON "RES"')
            self.ResetFormat()
        else:
            ## This just changes the buffer output format so only the 
            ## resistance string is output
            self.raw.write("FORM:ELEM:SENS RES\n")
        
    ## Sense ranges (and aliases)
    def vRange(self,volts):
        self.raw.write(":SENS:VOLT:RANG "+str(volts)+"\n")
        
    def vSenseRange(self,volts):
        self.vRange(volts)

    def cRange(self,current):
        self.raw.write(":SENS:CURR:RANG "+str(current)+"\n")
        
    def cSenseRange(self,current):
        self.cRange(current)

    ## Source mode
    def vSource(self):
        self.raw.write(":SOUR:FUNC VOLT\n")

    def cSource(self):
        self.raw.write(":SOUR:FUNC CURR\n")
        
    ## Source range (18-75 of manual; p. 455 of PDF)
    def vSourceRange(self,volts="auto"):
        volts_str = str(volts)
        if volts_str.lower() == "auto":
            self.raw.write(":SOUR:VOLT:RANG:AUTO 1\n")
        else:
            self.raw.write(":SOUR:VOLT:RANG "+volts_str+"\n")
        
    def cSourceRange(self,amps="auto"):
        amps_str = str(amps)
        if amps_str.lower() == "auto":
            self.raw.write(":SOUR:CURR:RANG:AUTO 1\n")
        else:
            self.raw.write(":SOUR:CURR:RANG "+amps_str+"\n")
        
    ## Beeper control
    def beepOFF(self):
        self.raw.write(":SYST:BEEP:STAT OFF\n")

    def beepON(self):
        self.raw.write(":SYST:BEEP:STAT ON\n")

    ## Enable/disable updating of LCD display (for extra instrument speed)
    def dispEnable(self):
        self.raw.write(":DISP:ENAB ON\n")

    def dispDisable(self):
        self.raw.write(":DISP:ENAB OFF\n")

    ## Front/rear terminals
    def frontTerminals(self):
        self.raw.write(":ROUT:TERM FRON\n")

    def rearTerminals(self):
        self.raw.write(":ROUT:TERM REAR\n")

    ## Output off-state
    def outputOffState(self,state='normal'):
        """
        See pp. 320-323 of 2400 PDF manual
        
        - Normal ('norm' or 'normal')
           Relatively high-impedance
           V-source selected and set to 0 V and current compliance is set to 
           0.5% of full-scale of the present range.
           
        - Zero ('zero')
           Measurements are performed and displayed.
           With V-source selected:
             V-source is set to 0 V internally and current compliance unchanged
           With I-source selected:
             V-source is selected internally and set to 0 V and current-
             compliance is set to the greater of the present I-source value or 
             0.5% of the full-scale range.
        
        - High Impedance ('hi' or 'himp')
           Relay opens and closes.  THIS WILL WEAR OUT THE RELAY if overused
           and also incurs a ~15 msec relay settling delay.
           
        - Guard ('guar', or 'guard')
           Good for 6-wire guarded resistance measurements and whenever using
           an active source.
           I-source is selected and set to 0 A.  Voltage compliance set to
           0.5% of full-scale of the present range.
        """
        if state.lower() == 'zero':
            strCmd = 'ZERO'
        elif (state.lower() == 'hi')|(state.lower() == 'himp'):
            strCmd = 'HIMP'
        elif (state.lower() == 'norm')|(state.lower() == 'normal'):
            strCmd = 'NORM'
        elif (state.lower() == 'guar')|(state.lower() == 'guard'):
            strCmd = 'GUAR'
        else:
            strCmd = 'NORM' # return to "normal" off state
        self.raw.write(':OUTP:SMOD '+ strCmd +'\n')
        
    def outputOffStateZero(self):
        """
        Output off-state "Zero"
        
        See "outputOffState"
        """
        self.raw.write(":OUTP:SMOD ZERO\n")
    def outputOffStateGuard(self):
        """
        Output off-state "Guard"
        
        See "outputOffState"
        """
        self.raw.write(":OUTP:SMOD GUAR\n")
    def outputOffStateNormal(self):
        """
        Output off-state "Normal"
        
        See "outputOffState"
        """
        self.raw.write(":OUTP:SMOD NORM\n")
    def outputOffStateHI(self):
        """
        Output off-state "High Impedance"
        
        See "outputOffState"
        """
        self.raw.write(":OUTP:SMOD HIMP\n")
        
    # 2- or 4-wire sensing (see p. 120 of 2410 PDF manual)
    def twoWire(self):
        self.raw.write(":SYST:RSEN OFF\n")
     
    def fourWire(self):
        """
        4-wire sensing.
        
        - When measuring V in either case (source V or I), you get a better
        measurement that excludes drop in the sense lines.
        ** There is another BIG implication to this **
        - When sourcing V, *the actual Vsource is increased/decreased*
        until the voltage measured on the sense lines is
        equal to the original Vsource setpoint.
        ** LIMITING Vsource via the source protection setting is crucial **
        - see 2-6 (or p. 78) and 3-11 (pp. 96-97) of 2410 PDF manual
        """
        self.raw.write(":SYST:RSEN ON\n")
        print("4-wire mode enabled.\n")
        print("** USE CAUTION if sourcing voltage, be sure the sense wires are attached. **\n")
        print('\nTo be sure the sense wires are attached, do this (when set to source V):\n'+\
        '  1) Press LOCAL on the front panel\n'+\
        '  2) Press the MEAS V button to select the voltage measured on the sense wires.\n'+\
        '  3) Turn the output ON, and OFF quickly if the voltage is moving around a lot,'+\
        ' which implies one or both sense wires are not attached properly.\n')
        self.raw.write(":SOUR:VOLT:PROT MIN\n")
        print("To offer *partial* protection of your device under test, "+\
        " source voltage protection is now enabled at its minimum value "+\
        "(20V on 2410, 6V on 2420).")
    
    def vProtect(self,volts):
        """
        Limit the voltage source.
        
        See Keithley manual 18-80+, i.e. page 460 of PDF.
        """
        self.raw.write(":SOUR:VOLT:PROT " + str(volts) + "\n")
        
    def SETUP_FOR_PV(self, iMax=100e-3, vSrcRng=1.):
        """
        Setup for PV measurements.
        """
        self._fasterMeas_ = True  # use the "fast" measurement mode
        self.reset()
        self.rearTerminals()
        self.vSource()    # source V
        self.cvSense()    # measure I and V simultaneously
        self.vProtect(1)  # basically use the minmum protection range
        self.twoWire()
        self.beepOFF()
        self.outputOffStateGuard() # good for active source like a PV cell
        self.vSourceRange(vSrcRng)  # e.g. 1.0 yields up to 2.1 V on 2410 (2420?)
        self.cCompliance(iMax)
        self.cSenseRange(iMax)
        self.timeAutoResetOFF() # keep the internal clock running
        self.timeReset() # ...but reset it to 0 to start out
        print('\nReady to go using 2-wire REAR terminals.\n'+\
              'Turn the output ON, then issue .setVolts(V) '+\
              'and .getVI(N) commands.\n')
        
        
        
## ****************************************************************************    



## ****************************************************************************    
## A class for the Keithley 6517A electrometer
## ****************************************************************************    
class electrometer():
    
    def __init__(self, portNum=27):
        """
        Note: SCPI command summary starting on p. 193 of the PDF 6517A manual,
        or section-page 3-42.
        """
        # if portNum = 0 or 1, assumed to be COM; otherwise, GPIB address
        # defaults to GPIB address 27
        self._serPort_ = None
        self._GPIBaddr_ = None
        if portNum < 2:
            self._serPort_ = portNum
        else:
            self._GPIBaddr_ = portNum            
        self.openport()        # open the port
        self.zeroCheckQuery()  # Get the present ZeroCheck status (stored as an attribute "._zchStatus_" of instrument instance)
    
    def openport(self):
        if self._GPIBaddr_ == None:
            self.raw=serial.Serial(self._serPort_,9600,timeout=1) #open the serial on COM n+1
        else:
            self.raw = instrument('GPIB::' + str(self._GPIBaddr_) + '::INSTR')
        self.zeroCheckQuery()  # Get the present ZeroCheck status (stored as an attribute "._zchStatus_" of instrument instance)
    
    def closeport(self):
        self.raw.close()
    
    ## 
    def ParseLine(self,outputLine):
        """
        number = ParseLine(outputLine)
        
        Use this function internally, self.ParseLine(outputLine), to return a 
        proper floating point number for any measurement.
        
        By default, the 6517A spits out an ASCII string, but it contains
        a few comma separated values, e.g. this voltage reading:
        
            '+0.519535E+00NVDC,+0004184.763249secs,+12966rdng#'
            
        We only want the data, so we first split by the ',' and grab the first
        substring.  Then we split by the status letter that immediately follows
        the numerical string.Template
        
        See 6517A manual p. 226-7 of PDF or 3-75+
        
        """
        dataStr = outputLine.split(',')[0]
        if dataStr.__contains__('N'):
            # then we have a "normal" reading
            number = float(dataStr.split('N')[0])
        elif dataStr.__contains__('Z'):
            # then zero check is enabled
            number = float(dataStr.split('Z')[0])
            print('ZeroCheck is enabled, no measurement made.\n')
        elif dataStr.__contains__('R'):
            # This is a reference
            number = float(dataStr.split('R')[0])
            print('Reference value returned by measurement.\n')
        else:
            number = 0.
            print('Error upon measurement; ' + dataStr + ' was returned.\n')
        return number

    #----------------------------------------------
    # Meter setup commands
    #----------------------------------------------
    
    # Reset
    def reset(self):
        """
        Resets the instrument to defaults, but also adds a safety twist:
        
        See table 2-30 in 6517A manual for factory defaults, on pp. 133+ of PDF 
        or 2-111+.
        
        ** Also turns the voltage source limit ON and sets it to +/- 5 V **
        (see 6517A PDF p. 259 (3-108)
        """
        print('Resetting 6517A to defaults...\n')
        self.raw.write("*RST\n")
        self._zchStatus_ = self.zeroCheckQuery()
        ## Also, do a bit of lab safety
        time.sleep(2)  # wait for reset to complete
        print('Making a couple changes to default settings...\n')
        self.raw.write(":SOUR:VOLT:LIM:STAT ON \n")   # turn on the source voltage limit
        self.raw.write(":SOUR:VOLT:LIM 5\n")          # set the limit to +/- 5 V
        print(" - 6517A voltage source limited to +/- 5 V for safety.")
        print("(Use instance.vSourceLimit(V) to change the limit or instance.vSourceLimitOFF() to disable limiting.)\n")
        ## And turn off the polynomial math mode it defaults to
        self.raw.write(':CALC:FORM NONE')  # see 6517A manual section 2.18 and 3.13
        print(' - 6517A defaults to \'polynomial\' math mode; turned it off\n.')
        ## Enable continuous measurement
        self.raw.write(':init:cont on')    # more intuitive...continual updates...use ':fetch?' to read
        print(' - 6517A defaults to one-off measurements; continuous measurement enabled.\n')
        
    def queryOutputFormat(self):
        """
        Returns a comma separated list of the elements included in the
        data returned by the instrument.
        
        See 3.16 of the 6517A manual for details, i.e. pp. 228+ of the PDF or 
        3-77+.
        """
        line = self.raw.ask(':form:elem?')
        return line
        
    def queryOutputTraceFormat(self):
        """
        Returns a comma separated list of the elements included in the
        **TRACE** data returned by the instrument.
        
        See 3.23.7 of the 6517A manual for details, i.e. pp. 292+ of the PDF or 
        3-141+.
        """
        line = self.raw.ask(':trace:elem?')
        return line
        
    # Compliance levels
    def vCompliance(self,volts):
        self.raw.write(":SENS:VOLT:PROT "+str(volts)+"\n")

    def cCompliance(self,current):
        #self.raw.write(":SENS:CURR:PROT "+str(current)+"\n")
        ## The above line doesn't work on the 6517, so limit the source current
        print('The 6517 does not have a very configurable current limit.\n'+\
        'Normal limit is about 1 mA. (See manual pdf p. 259 for options.)\n')

    ## Sense mode
    def querySenseMode(self):
        strOut = self.raw.ask(':FUNC?')  # it doesn't work with a newline character (GPIB tested)
        return strOut
    
    def SwitchSenseMode(self,newMode='VOLT'):
        """
        Master function to select the measurement mode
        
        See 6517A manual PDF p. 236 (3-85)
        """
        ## First verify that ZeroCheck is ON
        if self._zchStatus_:
            self.raw.write(":FUNC '" + newMode + "'")  # need the single quotes in the string around 'newMode'
            success=True
        else:
            print('You must turn ZeroCheck ON first; mode not changed.')
            success=False
        return success
        
    def vSense(self):
        """
        Select voltage measurement mode.  Returns "success" as True or False.
        """
        #self.raw.write("FORM:ELEM:SENS VOLT\n")
        #self.raw.write(":FUNC 'VOLT'\n")
        success = self.SwitchSenseMode('VOLT')
        return success

    def cSense(self):
        """
        Select current measurement mode.
        
        6517A PDF p. 236 (3-85)
        """
        #self.raw.write("FORM:ELEM:SENS CURR\n")
        #self.raw.write(":FUNC 'CURR'\n")
        success = self.SwitchSenseMode('CURR')
        return success
        
    def rSense(self):
        """
        Select resistance measurement mode.
        
        6517A PDF p. 236 (3-85)
        """
        #self.raw.write("FORM:ELEM:SENS RES\n")
        #self.raw.write(":FUNC 'RES'\n")
        success = self.SwitchSenseMode('RES')
        return success
        
    def qSense(self):
        """
        Select charge measurement mode.
        
        6517A PDF p. 236 (3-85)
        """
        #self.raw.write("FORM:ELEM:SENS CHAR\n")
        #self.raw.write(":FUNC 'CHAR'\n")
        success = self.SwitchSenseMode('CHAR')
        return success
    
    # Set sense range
    def vRange(self,volts):
        """
        Set the voltage measurement range.  Enter the maximum expected reading 
        in volts.
        
        Possible "volts" input options:
         - 'DEF' for default (210)
         - 'MIN' for the minimum (0)
         - 'MAX' for the maximum (same as default)
         - 'AUTO' to set automatically
         - 'AUTOONCE' to set automatically, but then turn off the auto mode.
         - N, where N is any number between 0 and 210
        
        6517A PDF p. 241 (3-90)
        """
        baseStr = ":SENS:VOLT:RANG"
        ## Do something different based on what was input
        if volts.__class__() == '':
            # If it is a string...
            if volts.lower() == 'auto':
                self.raw.write(baseStr + ":AUTO ON\n")
            elif volts.lower() == 'autoonce':
                self.raw.write(baseStr + ":AUTO ONCE\n")
            else:
                self.raw.write(baseStr + " " + volts.upper() + "\n")
        else:
            # It must be a number
            ## Make sure it is in range
            if np.logical_and(volts >= 0., volts <= 210.):
                self.raw.write(baseStr + " " + volts.__str__() + "\n")
            else:
                print("Requested range not valid; must be 0 <= volts <= 210.\n")

    def cRange(self,current):
        """
        Set the current measurement range.  Enter the maximum expected reading 
        in amps.
        
        Possible "current" input options:
         - 'DEF' for default (21e-3 amps)
         - 'MIN' for the minimum (0)
         - 'MAX' for the maximum (same as default)
         - 'AUTO' to set automatically
         - 'AUTOONCE' to set automatically, but then turn off the auto mode.
         - N, where N is any number between 0 and 21e-3 amps
        
        6517A PDF p. 241 (3-90)
        """
        baseStr = ":SENS:CURR:RANG"
        ## Do something different based on what was input
        if current.__class__() == '':
            # If it is a string...
            if current.lower() == 'auto':
                self.raw.write(baseStr + ":AUTO ON\n")
            elif current.lower() == 'autoonce':
                self.raw.write(baseStr + ":AUTO ONCE\n")
            else:
                self.raw.write(baseStr + " " + current.upper() + "\n")
        else:
            # It must be a number
            ## Make sure it is in range
            if np.logical_and(current >= 0., current <= 21e-3):
                self.raw.write(baseStr + " " + current.__str__() + "\n")
            else:
                print("Requested range not valid; must be 0 <= current <= 21e-3 A.\n")
    
    def qRange(self,charge):
        """
        Set the charge measurement range.  Enter the maximum expected reading 
        in coulombs.
        
        Possible "charge" input options:
         - 'DEF' for default (2.1e-6 C)
         - 'MIN' for the minimum (0)
         - 'MAX' for the maximum (same as default)
         - 'AUTO' to set automatically
         - 'AUTOONCE' to set automatically, but then turn off the auto mode.
         - N, where N is any number between 0 and 2.1e-6 coulombs
        
        6517A PDF p. 241 (3-90)
        """
        baseStr = ":SENS:CHAR:RANG"
        ## Do something different based on what was input
        if charge.__class__() == '':
            # If it is a string...
            if charge.lower() == 'auto':
                self.raw.write(baseStr + ":AUTO ON\n")
            elif charge.lower() == 'autoonce':
                self.raw.write(baseStr + ":AUTO ONCE\n")
            else:
                self.raw.write(baseStr + " " + charge.upper() + "\n")
        else:
            # It must be a number
            ## Make sure it is in range
            if np.logical_and(charge >= 0., charge <= 2.1e-6):
                self.raw.write(baseStr + " " + charge.__str__() + "\n")
            else:
                print("Requested range not valid; must be 0 <= charge <= 2.1e-6 C.\n")
                
    def rRange(self,ohms):
        """
        Set the resistance measurement range.  Enter the maximum expected 
        reading in ohms.  
        
        OPERATES DIFFERENTLY DEPENDING ON AUTO OR MANUAL V-SOURCE MODE.
        
        Possible "charge" input options:
         - 'DEF' for default (100e18 ohms in auto v-source, 21e3 in manual)
         - 'MIN' for the minimum (0)
         - 'MAX' for the maximum (same as default)
         - 'AUTO' to set automatically
         - 'AUTOONCE' to set automatically, but then turn off the autoranging.
         - N, where N is any number between 0 and 100e18 ohms in auto v-source
         or 21e3 ohms in manual v-source mode.
        
        6517A PDF pp. 244-5 (3-92+)
        """
        ## Query the mode
        
        baseStr = ":SENS:RES:RANG"  # use when in auto v-source mode
        upperLim = 100e18
        baseStr = ":SENS:RES:"      # use when in manual v-source mode
        upperLim = 21e3
        ## Do something different based on what was input
        if ohms.__class__() == '':
            # If it is a string...
            if ohms.lower() == 'auto':
                self.raw.write(baseStr + ":AUTO ON\n")
            elif ohms.lower() == 'autoonce':
                self.raw.write(baseStr + ":AUTO ONCE\n")
            else:
                self.raw.write(baseStr + " " + ohms.upper() + "\n")
        else:
            # It must be a number
            ## Make sure it is in range
            if np.logical_and(ohms >= 0., ohms <= upperLim):
                self.raw.write(baseStr + " " + ohms.__str__() + "\n")
            else:
                print("Requested range not valid; must be 0 <= ohms <= " + ohms.__str__() + "ohms.\n")
    
    # Number of power-line cycles to integrate
    def integrateNPLC(self,nplc='DEF'):
        """
        Set the number of power-line cycles to integrate for each meas. point.
        ** This will be changed only for the active sense mode **
        
        Possible "nplc" input options:
         - 'DEF' for default (1)
         - 'MIN' for the minimum (0.01)
         - 'MAX' for the maximum (10)
         - 'AUTO' to set automatically based on the resolution
         - 'AUTOONCE' to set automatically based on the resolution, but then
            turn off the auto mode.
         - N, where N is any number between 0.01 and 10
        
        6517A PDF p. 240 (3-89)
        """
        ## First we need to determine the active measurement mode
        presMode = self.querySenseMode()  # query the sense function
        modeLower = presMode.lower()
        if modeLower.__contains__('volt'):
            funcStr = ':VOLT'
        elif modeLower.__contains__('curr'):
            funcStr = ':CURR'
        elif modeLower.__contains__('res'):
            funcStr = ':RES'
        elif modeLower.__contains__('char'):
            funcStr = ':CHAR'
        else:
            print('Unable to determine present sense mode; assuming voltage.')
            funcStr = ':VOLT'
        ## Do something different based on what was input
        if nplc.__class__() == '':
            # If it is a string...
            if nplc.lower() == 'auto':
                self.raw.write(funcStr + ":NPLC:AUTO ON\n")
            elif nplc.lower() == 'autoonce':
                self.raw.write(funcStr + ":NPLC:AUTO ONCE\n")
            else:
                self.raw.write(funcStr + ":NPLC " + nplc.upper() + "\n")
        else:
            # It must be a number
            ## Make sure it is in range
            if np.logical_and(nplc >= 0.01, nplc <= 10.):
                self.raw.write(funcStr + ":NPLC " + str(nplc) + "\n")
            else:
                print("Requested NPLC number out of range; must be 0.01 < nplc < 10.\n")

    #----------------------------------------------
    # Measurement commands
    #----------------------------------------------
    
    def zeroCheckON(self):
        """
        Turn ON "ZeroCheck", which should be done prior to switching
        measurement modes (e.g. V -> I) or making any connections
        
        6517A PDF p. 206 (3-55) and 284 (3-133)
        """
        self.raw.write(":SYST:ZCH ON\n")
        foo = self.zeroCheckQuery()  # change the ._zchStatus_ attribute
        
    def zeroCheckOFF(self):
        """
        Turn OFF "ZeroCheck"
        
        6517A PDF p. 206 (3-55) and 284 (3-133)
        """
        self.raw.write(":SYST:ZCH OFF\n")
        foo = self.zeroCheckQuery()  # change the ._zchStatus_ attribute
        
    def zeroCheckQuery(self):
        """
        zchStatus = zeroCheckQuery()
        
          -> returns True or False
        """
        #strBack = self.raw.ask(":SYST:ZCH?\n")  # it doesn't like the new line (GPIB tested)
        strBack = self.raw.ask(":SYST:ZCH?")
        if strBack.__contains__('1'):
            self._zchStatus_ = True
        else:
            self._zchStatus_ = False
        return self._zchStatus_

    def zeroCorrectON(self):
        """
        Turn ON "ZeroCorrect" (turns on ZeroCheck first)
        
        6517A PDF p. 284 (3-133)
        """
        ## Need to turn ZeroCheck on first
        self.zeroCheckON()
        ## Now zero correct
        self.raw.write(":SYST:ZCOR ON\n")
        
    def zeroCorrectOFF(self):
        """
        6517A PDF p. 284 (3-133)
        """
        self.raw.write(":SYST:ZCOR OFF\n")
        
    #def zeroCorrectValue(self):
    #    """
    #    Check 3.22.12...
    #    """
    #    #self.raw.write(':syst:zcor:acq')
        
    def enableContinuous(self):
        self.raw.write(':init:cont on')    # more intuitive...continual updates...use ':fetch?' to read
        
    ## Measure things
    def GetAnything(self,measType='VOLT'):
        """
        A master function to make a measurement, called by the others
        
        Call internally using:  number = self.GetAnything('VOLT')
        ...or 'CURR', 'RES', 'CHAR'
        
        See 6517A PDF manual p. 191 (3-40).
        """
        ## Make sure the present mode jives with the "measType"
        presMode = self.querySenseMode()
        if presMode.__contains__(measType):
            ## Make sure zeroCheck is off
            if self._zchStatus_:  # faster than querying the status from the instrument
                print('ZeroCheck is on...you must turn it off first using instance.zeroCheckOFF()\n')
                number = 0.
            else:
                #strOut = self.raw.ask(':MEAS:' + measType + '?') # doesn't work with a newline character (GPIB)
                strOut = self.raw.ask(':FETCH?') # faster than :meas:volt?
                number = self.ParseLine(strOut)
        else:
            print('You asked for a \'' + measType + 
            '\' meas., but the present mode is ' + presMode + '.\n')
            number = 0.
        return number
        
    def getVolts(self):
        number = self.GetAnything('VOLT')
        return number
        
    ## Measure N samples of voltage and return the array
    def getVoltsN(self,N=10):
        voltsN = np.zeros(N)  # initialize the array
        for ii in range(N):
            voltsN[ii] = self.getVolts()            
        return voltsN
        
    ## Measure current once
    def getCurrent(self):
        number = self.GetAnything('CURR')
        return number
        
    ## Measure N samples of current and return the array
    def getCurrentN(self,N=10):
        ampsN = np.zeros(N)  # initialize the array
        for ii in range(N):
            ampsN[ii] = self.getCurrent()
        return ampsN

    ## Measure resistance once
    def getResistance(self):
        number = self.GetAnything('RES')
        return number
        
    ## Measure N samples of resistance and return the array
    def getResistanceN(self,N=10):
        ohmsN = np.zeros(N)  # initialize the array
        for ii in range(N):
            ohmsN[ii] = self.getResistance()
        return ohmsN
        
    ## Measure charge once
    def getCharge(self):
        number = self.GetAnything('CHAR')
        return number
        
    ## Measure N samples of charge and return the array
    def getChargeN(self,N=10):
        coulN = np.zeros(N)  # initialize the array
        for ii in range(N):
            coulN[ii] = self.getCharge()
        return coulN
    
    
    #----------------------------------------------
    # Voltage source control
    #----------------------------------------------
    ## Set the limit and turn it on or off
    def vSourceLimit(self,volts):
        """      
        ** Turns the voltage source limit ON and sets it to +/- "volts" **
        (see 6517A PDF p. 259 (3-108)
        """
        self.raw.write(":SOUR:VOLT:LIM "+ volts.__str__() +"\n")          # set the limit to +/- input "volts"
        self.raw.write(":SOUR:VOLT:LIM:STAT ON \n")   # turn on the source voltage limit
        print("\n6517A voltage source limited to +/- " + volts.__str__() + " V.\n")
        
    def vSourceLimitON(self):
        """      
        Enable the vSourceLimit
        (see 6517A PDF p. 259 (3-108)
        """
        self.raw.write(":SOUR:VOLT:LIM:STAT ON \n")   # turn ON the source voltage limit
        print("\nYou should define a 6517A voltage source limit too, using raw.vSourceLimit(V).\n")
        
    def vSourceLimitOFF(self):
        """      
        Disable the vSourceLimit
        (see 6517A PDF p. 259 (3-108)
        """
        self.raw.write(":SOUR:VOLT:LIM:STAT OFF \n")   # turn OFF the source voltage limit
        print("\n6517A voltage source now only limited by the range setting (probably +/- 100 V).\n")

    def vSourceInternalConn(self):
        """
        """
        self.raw.write(":SOUR:VOLT:MCON ON\n")
        print('6517A voltage source connected to internal ammeter.')
        print('Connect device under test (DUT) as shown on p. 55 of PDF manual (2-34): \n' + 
        '\t237-ALG-1 center conductor (red) to + end of DUT\n'+
        '\tVSource HI to the - end of DUT.\n'+
        '\t(VSource LO is internally connected to electrometer LO)\n')
        
    def vSourceInternalConnOFF(self):
        """
        """
        self.raw.write(":SOUR:VOLT:MCON OFF\n")
        print("6517A voltage source disconnected from internal ammeter.")
    
    def vSourceInternalConnON(self):
        """
        A convenience function for vSourceInternalConn
        """
        self.vSourceInternalConn()
        
    ## Set the voltage
    def setVolts(self,volts):
        self.raw.write(":SOUR:VOLT "+str(volts)+"\n")

    ## Turn the voltage source output on or off
    def ON(self):
        """
        Turn ON the V-source
        
        6517A PDF p. 231 (3-80)
        """
        self.raw.write(":OUTP ON\n")
        
    def OFF(self):
        """
        Turn OFF the V-source
        """
        self.raw.write(":OUTP OFF\n")
    
    

## ****************************************************************************




## ****************************************************************************
class InstrTestingClass():
    """
    A class with some functions useful for testing the instrument(s).
    
    Usage example:
    
    import k_meter_ntg
    k2410 = k_meter_ntg.sourcemeter()           # instance the instrument
    test = k_meter_ntg.InstrTestingClass(k2410) # instance the test class for this instrument
    
    test.speedTest(N=50) # run a speed test on a 50 sample data collection
    
    test.speedTest(N=50) # run a speed test on a 50 sample data collection
    """
    
    def __init__(self,instance=None):
        """
        Runs upon initialization of the class.  
        
        Input an instrument "instance", or leave it blank to access functions 
        that don't require an active instance.
        """
        self._inst_ = instance # save the input instrument instance
    
    def SpeedTest(self,N=10,iORv ='i'):
        """
        Test speed of instrument at measuring "N" current or voltage values,
        returns tuple of time in seconds and the data itself
        """
        instance = self._inst_
        if instance == None:
            print('\nCannot run this function without an input instrument instance.\n')
            dt = 0
        else:
            if iORv.lower() == 'i':
                instance.cSense() # set up for current measurment
                methodToUse = instance.getCurrentN
            elif iORv.lower() == 'v':
                instance.vSense() # set up for voltage measurement
                methodToUse = instance.getVoltsN
            else:
                methodToUse = instance.getCurrentN
            t0 = time.time() # get present time in seconds
            if instance._fasterMeas_:
                dataOut = methodToUse(N,inclTime=True)
            else:
                dataOut = methodToUse(N)
            dt = time.time()-t0 # get elapsed time
        return (dt, dataOut)
    
    def speedComparison_24xx(self,N=10,iORv='i',dispDisable=False):
        """
        Function will close any active instrument instance and make new ones.
        """
        if self._inst_ != None:
            self._inst_.closeport()
        ## Try the 24xx, old-school
        k_old = sourcemeter()
        ## Disable the display, if requested
        if dispDisable:
            k_old.dispDisable()
        else:
            k_old.dispEnable()
        ## Now do measurement while timing
        self._inst_ = k_old # pass the instance into self attribute
        dt_old, dataOut = self.SpeedTest(N,iORv); mean_old = dataOut.mean()
        k_old.closeport() # close the comm port
        ## Now the 24xx again, using "fasterMeas"
        k_new = sourcemeter(fasterMeas=True); self._inst_ = k_new
        ## Now do measurement while timing
        dt_new, dataOut = self.SpeedTest(N,iORv); mean_new = dataOut[0].mean()
        dt_new_instr = dataOut[1][-1]-dataOut[1][0] # use timestamp
        k_new.closeport()
        print('\nOutput:\t(OLD time, OLD mean), '+\
              '\n\t(NEW time (Python), NEW time (instr), NEW mean)')
        return ((dt_old,mean_old), (dt_new,dt_new_instr,mean_new))


## ****************************************************************************